# comun self hosted implementation: compiler library
# module prefix: c
#
# This module contains resources that can be used to compile comun source code
# to the implementation specific comun BC. Doesn't do nontrivial optimizations.
#
# Before including this library, define the following:
#
# - CMN_cMEM_SIZE: Func returning a const, size of CMN_cMem in cells, in TE 32.
# - CMN_cMem:      Ptr to mem in TE 32 that will be used by com as a working
#                  mem, this affects symbol table size.
# - CMN_cInclude:  Func that will be called by the compiler when file include is
#                  encountered, with filename as traditional zero terminated str
#                  on stack in TE 0, which has to be popped by the func.
#
# The following are details concerning internal working of the com.
# 
# The com mem is organized like so:
#
#   S0C S0B S0A S0N  S1C S1B S1A S1N ...
#
# SxA/B/C encodes xth symbol in symbol tab., SxN directly stores the symbol val.
#
# The com DOESN'T USE A PARSE STACK, which makes the code simpler and reduces
# mem usage (but is potentially a bit slower). Instead it works right in the BC
# it is generating (i.e. it effectively uses the BC as stack). A small HACK is
# used here: we use the no-pop bit to mark unhandled instrs (the instrs we deal
# with here never make use of no-pop bit themselves, so we are free to use it
# temporarily). Once such instr is handled, we reset the no-pop bit (equivalent
# to popping it from a stack). The portions of code making use of this are
# commented with "HACK".
#
# Symbol table: array of pairs (pseudohash, N) where pseudohash is a 16 char
# value associated with identifier whose first symbol indicates the type of
# symbol, and N is the symbol value. A symbol can also be seen as having a
# value M, which is its order among all records of the same type (starting with
# same letter). The following are possibly symbol types:
#
#  f: func location, N = BC address of the func
#  0: ptr in TE 0, N = size (in cells), M = ptr ID
#  1: ptr in TE 8, ...
#  2: ptr in TE 16, ...
#  3: ptr in TE 32, ...
#  i: included file name (to prevent double includes), N is unused
#  c: yet unknown func call, N is call ID; this record is used when a yet
#     undefined func is called; in such case CAE (instead of CAL) is produced,
#     storing N, which can later be used to resolve previously unknown calls (or
#     determine the call to really be of external func).
#  g: goto ID, N is ID
#  l: label, N is addr

~"cmn_general.cmn"
~"cmn_bytecode.cmn"
~"cmn_tokenizer.cmn"

~CMN_cState         # compiler state

~32
  ~_CMN_cMemEnd:0   # last cell of CMN_cpMem
  ~_CMN_cInterState # internal state of com, structure is this:
                    # - 2 highest bits: current TE
                    # - 1 next highest bit: no pop bit
                    # - 1 next highest bit: default addr size
                    #   (0 -> 16bit, 1 -> 32bit)
                    # - 16 lowest bits: symbol table item count
                    # - rest of the bits: unknown jump count
~0

~16 ~_CMN_cBytecodeEnd:0 ~0    # last cell of CMN_bBytecode

# compiler states:
CMN_cSTATE_OK:                 0  . # all fine, continuing
CMN_cSTATE_DONE:               1  . # finished without error
CMN_cSTATE_ERROR:              2  . # further unspecified com error
CMN_cSTATE_ERROR_TOKEN:        3  . # error: badly formed token
CMN_cSTATE_ERROR_UNEXPECTED:   4  . # error: unexpected token or end
CMN_cSTATE_ERROR_NAME_UNK:     5  . # error: unknown name
CMN_cSTATE_ERROR_NAME_EXISTS:  6  . # error: name redefined
CMN_cSTATE_ERROR_SYMBOL_TAB:   7  . # error: symbol table ran out of mem
CMN_cSTATE_ERROR_BC_TOO_BIG:   8  . # error: ran out of mem for BC
CMN_cSTATE_ERROR_ADDR_TOO_BIG: 9  . # error: addr. too big (increase addr. size)
CMN_cSTATE_ERROR_UNSUPPORTED:  10 . # error: unsupported feature

# arg: n
# ret: c
# Converts lower 6 bits of n to ASCII char, for use by string pseudohash func
CMN_cNumToChar:
  64 %
  $0 26 < ?  "a" +       ;
  $0 52 < ?  26 - "A" +  ;
  $0 62 < ?  52 - "0" +  ;
             ^ "_" # stands for both 62 and 63
  . . .
.

# arg: c
# ret: n
# Performs the opposite of CMN_cNumToChar.
CMN_cCharToNum:
  $0 "a" >= $1 "z" <= & ?  "a" -       ;
  $0 "A" >= $1 "Z" <= & ?  "A" - 26 +  ;
  $0 "0" >= $1 "9" <= & ?  "0" - 52 +  ;
                           ^ 63
  . . .
.

# ret: te
# Gets currently set TE
_CMN_cCurrentTypeEnvGet: 0 ~32 $_CMN_cInterState 30 |> >0 ~0 .

# arg: te
_CMN_cCurrentTypeEnvSet:
  ~32 $_CMN_cInterState +x3fffffff & 0 ~0 >32 ~32 30 |< | $:_CMN_cInterState ~0
.

# ret: np
_CMN_cNoPopGet: 0 ~32 $_CMN_cInterState 29 |> 1 & >0 ~0 .

# arg: np
_CMN_cNoPopSet:
  ~32 $_CMN_cInterState +xdfffffff & 0 ~0 >32
  ~32 29 |< | $:_CMN_cInterState ~0
.

# arg: opcode c4
# If com state is OK, appends new instr with said opcode to BC, if needed then
# instr TE and no-pop bits are set according to _CMN_cInterState, IC is set to
# c4, which must be < 16 may (use another func for larger IC). On error (no more
# space) com state is set.
CMN_cEmitInstr:
  CMN_bCurrentInstrShift

  ~16 $CMN_bCurrentInstr=_CMN_cBytecodeEnd 1 != ? ~0
    $1 CMN_bOpcodeUsesTypeEnv _CMN_cCurrentTypeEnvGet 0 ??
    >< 
    $2 CMN_bOpcodeUsesNoPop _CMN_cNoPopGet 0 ??
    >< 0
    CMN_bMakeInstr ~16 0 ~0 >16 ~16 $:CMN_bCurrentInstr ~0
  ;
    CMN_bCurrentInstrShiftBack
    CMN_cSTATE_ERROR_BC_TOO_BIG $:CMN_cState
  .
.

# If wishing to insert debug info int BS, call this after reading (and
# processing) each newline in source code.
CMN_cInsertDebugNewLine: CMN_bOPCODE_DES CMN_bDES_NEWLINE CMN_cEmitInstr .

# Similar to CMN_cInsertDebugNewLine, call on opening new included file.
CMN_cInsertDebugNewFile: CMN_bOPCODE_DES CMN_bDES_NEWFILE CMN_cEmitInstr .

# Similar to CMN_cInsertDebugNewLine, call on closing included file.
CMN_cInsertDebugEndFile: CMN_bOPCODE_DES CMN_bDES_ENDIF CMN_cEmitInstr .

# arg: 3c
# ret: n
# Returns size (in instrs) a constant will take to store.
CMN_cConstInstrSize: 0 ~32 1 @ ~0 ++ ~32 4 |> $0 . ^ ~0 .

# arg: 3c n
# Fills current instr and n - 1 following COCs with given IC.
_CMN_cFillConstN:
  0 _CMN_cNoPopSet

  @@
    ~16 0 ~0
    ~32 $0 >16 4 |> ~0
    ~16 +b1111 & 8 |< $CMN_bCurrentInstr +xe0ff & | $:CMN_bCurrentInstr ~0

    --
    ?'
      ~16 $CMN_bCurrentInstr +x1000 | $:CMN_bCurrentInstr ~0
      CMN_bOPCODE_COC 0 CMN_cEmitInstr
    ;
      ~32 ^ ~0
      !@
    .
  .
  ^
.

# arg: 3c
# Fills current instr with given IC, may append COC instrs to make it fit.
CMN_cFillConst: ~32 $0 ~0 CMN_cConstInstrSize _CMN_cFillConstN .

# arg: 3c1 3c2
# Fills current instr with given two ICs, may append COCs to make them fit.
CMN_cFillConsts:
  ~32 $1 $1 ~0 CMN_cConstInstrSize CMN_cConstInstrSize CMN_gMax
  $0
  ~32 >< ~0
  _CMN_cFillConstN
  ~16 $CMN_bCurrentInstr +x1000 | $:CMN_bCurrentInstr ~0
  CMN_bOPCODE_COC 0 CMN_cEmitInstr
  _CMN_cFillConstN
.

# ret: N
# Returns default size (in instrs) that should be allocated for unknown addr.
_CMN_cDefaultAddrSize: 0 ~32 $_CMN_cInterState 28 |> 1 & >0 ~0 8 4 ?? .

# arg: 3addr
# Fills back an addr (3addr) to instr pointed to by _CMN_g2P1, may overwrite
# some following instrs with COCs (needs preallocated unknown addr), if 3addr is
# too big for the space to fit, args are popped and error state of com is set.
_CMN_cFillBackAddr:
  ~32
    $0 0 ~0 _CMN_cDefaultAddrSize >32 ~32

    2 |< |> ? # any non-zero bits above maximum?
      ^ ~0 CMN_cSTATE_ERROR_ADDR_TOO_BIG $:CMN_cState ~32
    ;
      $CMN_bCurrentInstrAddr # back up
      >< 
      ~16 $_CMN_g2P1>CMN_bCurrentInstr ~32 # we ignore CMN_bCurrentInstrAddr
      CMN_cFillConst
      CMN_bCurrentInstrSetAddr
    .
  ~0
.

# Emits additional NOP instrs to hold place for later filling yet unknown addr.
_CMN_cPrepareUnknownAddr:
  _CMN_cDefaultAddrSize --
  @' CMN_bOPCODE_NOP 0 CMN_cEmitInstr -- .
  ^
.

# ret: 3n
# Pushes number of unknown jump encountered so far. 
_CMN_cUnknownJumpCount: ~32 $_CMN_cInterState 16 |> +x0fff & ~0 .

_CMN_cUnknownJumpCountInc:
  ~32
    _CMN_cUnknownJumpCount +x0fff != ?
      $_CMN_cInterState +x00010000 + $:_CMN_cInterState
    ;
      ~0 CMN_cSTATE_ERROR $:CMN_cState ~32
    .
  ~0
.

# ret: succ
# Helper func, expects _CMN_g2P1 to point to an instr within BC, will seek this
# ptr to the nearest previous DES instr that has no-pop bit set (see HACK). Also
# increments num in _CMN_gV1 by steps performed. Returns 1 (OK) or 0 (error).
_CMN_cSeekNextInstrMatch:
  @@ # WARNING: relying on reserved BC bits to be 0 to potentially stop the loop
    ~16
      $_CMN_g2P1 ?'
        ~0 0 ~16 >0
        ~0
          $0 CMN_bInstrOpcode CMN_bOPCODE_DES = >< +x2000 & && ?
            1 # succ
            !@
          .
        ~16
      ;
        ~0 0 ~16 # fail
        ^ !@
      .

      $<_CMN_g2P1
      ~0 $_CMN_gV1 ++ $:_CMN_gV1 ~16
    ~0
  .
.

# ret: 2bool
# Helper func, moves code from CMN_cFeedChar, returns 1 (matched) or 0 in TE 16.
_CMN_cHandleSimpleCommand:
  ~16 1 ~0 # result

  # first check control structures, push 0/1/2/3/4/5 (none/?/@/@@/!@/!.)

  0 "?"  CMN_tTokenMatch ? 1 ;
  0 "@"  CMN_tTokenMatch ? 2 ;
  0 "@@" CMN_tTokenMatch ? 3 ;
  0 "!@" CMN_tTokenMatch ? 4 ;
  0 "!." CMN_tTokenMatch ? 5 ;
    0
  . . . . .

  ?'
    CMN_bOPCODE_DES

    $1 1 = ? CMN_bDES_IF ;
    $1 4 = ? CMN_bDES_BREAK ;
    $1 5 = ? CMN_bDES_EXIT ;
             CMN_bDES_LOOP
    . . .

    CMN_cEmitInstr

    ~16 $CMN_bCurrentInstr +x2000 | $:CMN_bCurrentInstr ~0 # HACK

    $0 3 != ?
      $0 4 >= CMN_bOPCODE_JMA CMN_bOPCODE_JNA ?? 0 CMN_cEmitInstr
     _CMN_cPrepareUnknownAddr
    .
    ^

    !.
  .

  ^ # control structure type

  0 ";" CMN_tTokenMatch ?
    CMN_bOPCODE_DES CMN_bDES_ELSE CMN_cEmitInstr

    ~16 $CMN_bCurrentInstr>_CMN_g2P1 ~0 

    @@ # find what the end matches
      _CMN_cSeekNextInstrMatch

      ? # succ?
        0 ~16 $_CMN_g2P1 >0 ~0 8 |> +x0f & 

        $0 CMN_bDES_IF = ? # else matched if, nice
          ~16
            $_CMN_g2P1 +xdfff & $:_CMN_g2P1                 # unmark the if
            $CMN_bCurrentInstr +x2000 | $:CMN_bCurrentInstr # mark the else
            $>_CMN_g2P1                                     # move to jump ins.
          ~0

          ~32 0 ~0 _CMN_cDefaultAddrSize >32
          ~32 $CMN_bCurrentInstrAddr + ++ _CMN_cFillBackAddr ~0

           CMN_bOPCODE_JMA 0 CMN_cEmitInstr
           _CMN_cPrepareUnknownAddr
          !@
        ;
        $0 CMN_bDES_BREAK = $1 CMN_bDES_EXIT = | $1 CMN_bDES_GOTO = | ?
          # ignore breaks etc., just move to next instr.
          ~16 $<_CMN_g2P1 ~0
        ;
          CMN_cSTATE_ERROR_UNEXPECTED $:CMN_cState
          !@
        . .

        ^
      ;
        CMN_cSTATE_ERROR_UNEXPECTED $:CMN_cState
        !@
      .
    .

    !.
  .

  0 "." CMN_tTokenMatch ?
    ~16 $CMN_bCurrentInstr>_CMN_g2P1 ~0 # for match search 
    0 $:_CMN_gV1                        # will hold addr decrement

    @@
      _CMN_cSeekNextInstrMatch

      ? # success?
        0 ~16 $_CMN_g2P1 >0 ~0 8 |> +x0f &

        $0 CMN_bDES_IF = $1 CMN_bDES_ELSE = | ? # end matched if or else
          ~16
            $_CMN_g2P1 +xdfff & $:_CMN_g2P1     # unmark the if
            $>_CMN_g2P1                         # move to jump instr
          ~0
              
          ~32 $CMN_bCurrentInstrAddr ++ ++ _CMN_cFillBackAddr ~0

          CMN_bOPCODE_DES CMN_bDES_ENDIF CMN_cEmitInstr
          !@
        ;
        $0 CMN_bDES_LOOP = ? # end matched loop
          ~32 $CMN_bCurrentInstrAddr ++ 0 ~0
          $_CMN_gV1 >32 # addr decrement
          ~32 - ~0 # now we have the jump back addr on stack 32 for later

          0 # make room for instr type

          ~16
            $_CMN_g2P1 +xdfff & $:_CMN_g2P1 # unmark the DES
            $>_CMN_g2P1                     # move to next instr (jump)
            $_CMN_g2P1 >0                   # move instr type to stack 0
          ~0

          # compute the final (after loop) jump addr. on stack 32:
          ~32 $0 0 ~0 CMN_cConstInstrSize >32
          ~32 $CMN_bCurrentInstrAddr ++ ++ + ~0

          # now handle loop breaks, go instr by instr:
          CMN_bOPCODE_DES 0 1 CMN_bDES_BREAK 0 CMN_bMakeInstr # for comparison

          ~16
            $_CMN_g2P1>_CMN_g2P2

            1 @
              ~0 $0 0 ~16 $_CMN_g2P1 >0

              ~0 = ? ~16
                $_CMN_g2P1 +xdfff & $:_CMN_g2P1
                $>_CMN_g2P1
                ~32 $0 _CMN_cFillBackAddr ~16 # fill in break addr.
              .

              $>_CMN_g2P1
              $_CMN_g2P1=CMN_bCurrentInstr 2 =
            .

            $_CMN_g2P2>_CMN_g2P1
          ~0

          ^ # the comparison instr

          CMN_bInstrOpcode CMN_bOPCODE_JNA = ? # non-infinite loop?
            ~32 $0 _CMN_cFillBackAddr ~0       # then fill back jump addr.
          .

          ~32 ^ ~0 # after-loop jump addr.

          # now just append the final instrs
          CMN_bOPCODE_DES CMN_bDES_ENDLOOP CMN_cEmitInstr
          CMN_bOPCODE_JMA 0 CMN_cEmitInstr
          CMN_cFillConst # fill the above jump addr
          !@
        ;
        $0 CMN_bDES_FUNDEF = ? # end matched func definition
          ~16
             $_CMN_g2P1 +xdfff & $:_CMN_g2P1 # unmark, HACK
             $>_CMN_g2P1
          ~0

          ~32 $CMN_bCurrentInstrAddr ++ ++ _CMN_cFillBackAddr ~0

          CMN_bOPCODE_RET 0 CMN_cEmitInstr

          ~16 # now go backwards from RET and fill back addr of every exit
            $CMN_bCurrentInstr>_CMN_g2P1

            @@
              $<_CMN_g2P1
              ~0 0 ~16 $_CMN_g2P1 >0

              ~0
                $0 CMN_bInstrOpcode CMN_bOPCODE_DES = ?
                  $0 8 |> +x0f &

                  $0 CMN_bDES_FUNDEF = ?
                    !@
                  ;
                  $0 CMN_bDES_EXIT = $2 +x2000 & && ?
                    ~16
                      $_CMN_g2P1 +xdfff & $:_CMN_g2P1 # unmark
                      $>_CMN_g2P1
                    ~0

                    ~32 $CMN_bCurrentInstrAddr _CMN_cFillBackAddr ~0
                    ~16 $<_CMN_g2P1 ~0
                  . .

                  ^
                .
                ^
              ~16
            .
          ~0

          !@
        ;
          # breaks, exits, ... just ignore here
          ~16 $<_CMN_g2P1 ~0
          $_CMN_gV1 ++ $:_CMN_gV1
        . . .

        ^ # DES
      ;
        CMN_cSTATE_ERROR_UNEXPECTED $:CMN_cState
        !@
      . # match succ if
    . # match-search loop

    !.
  . # "." token

  0 "+"   CMN_tTokenMatch ? CMN_bOPCODE_ADX 0 CMN_cEmitInstr !. .
  0 "++"  CMN_tTokenMatch ? CMN_bOPCODE_ADC 1 CMN_cEmitInstr !. . 
  0 "-"   CMN_tTokenMatch ? CMN_bOPCODE_SUX 0 CMN_cEmitInstr !. .
  0 "--"  CMN_tTokenMatch ? CMN_bOPCODE_SUC 1 CMN_cEmitInstr !. .
  0 "*"   CMN_tTokenMatch ? CMN_bOPCODE_MUX 0 CMN_cEmitInstr !. .
  0 "/"   CMN_tTokenMatch ? CMN_bOPCODE_DIX 0 CMN_cEmitInstr !. .
  0 "//"  CMN_tTokenMatch ? CMN_bOPCODE_DSX 0 CMN_cEmitInstr !. .
  0 "%"   CMN_tTokenMatch ? CMN_bOPCODE_MOX 0 CMN_cEmitInstr !. .
  0 "%%"  CMN_tTokenMatch ? CMN_bOPCODE_MSX 0 CMN_cEmitInstr !. .
  0 "><"  CMN_tTokenMatch ? CMN_bOPCODE_SWP 0 CMN_cEmitInstr !. .
  0 "^"   CMN_tTokenMatch ? CMN_bOPCODE_POP 0 CMN_cEmitInstr !. .
  0 "="   CMN_tTokenMatch ? CMN_bOPCODE_EQX 0 CMN_cEmitInstr !. .
  0 "!="  CMN_tTokenMatch ? CMN_bOPCODE_NEX 0 CMN_cEmitInstr !. . 
  0 "<"   CMN_tTokenMatch ? CMN_bOPCODE_SMX 0 CMN_cEmitInstr !. .
  0 "<<"  CMN_tTokenMatch ? CMN_bOPCODE_SSX 0 CMN_cEmitInstr !. .
  0 "<="  CMN_tTokenMatch ? CMN_bOPCODE_SEX 0 CMN_cEmitInstr !. .
  0 "<<=" CMN_tTokenMatch ? CMN_bOPCODE_LSX 0 CMN_cEmitInstr !. .
  0 ">"   CMN_tTokenMatch ? CMN_bOPCODE_GRX 0 CMN_cEmitInstr !. .
  0 ">>"  CMN_tTokenMatch ? CMN_bOPCODE_GSX 0 CMN_cEmitInstr !. .
  0 ">="  CMN_tTokenMatch ? CMN_bOPCODE_GEX 0 CMN_cEmitInstr !. .
  0 ">>=" CMN_tTokenMatch ? CMN_bOPCODE_BSX 0 CMN_cEmitInstr !. .
  0 "||"  CMN_tTokenMatch ? CMN_bOPCODE_LOX 0 CMN_cEmitInstr !. .
  0 "&&"  CMN_tTokenMatch ? CMN_bOPCODE_LAX 0 CMN_cEmitInstr !. .
  0 "??"  CMN_tTokenMatch ? CMN_bOPCODE_CND 0 CMN_cEmitInstr !. .
  0 "!!"  CMN_tTokenMatch ? CMN_bOPCODE_EQC 0 CMN_cEmitInstr !. . 
  0 "|!!" CMN_tTokenMatch ? CMN_bOPCODE_LXX 0 CMN_cEmitInstr !. .
  0 "|"   CMN_tTokenMatch ? CMN_bOPCODE_BOX 0 CMN_cEmitInstr !. .
  0 "&"   CMN_tTokenMatch ? CMN_bOPCODE_BAX 0 CMN_cEmitInstr !. .
  0 "!"   CMN_tTokenMatch ? CMN_bOPCODE_BNO 0 CMN_cEmitInstr !. .
  0 "|!"  CMN_tTokenMatch ? CMN_bOPCODE_BXX 0 CMN_cEmitInstr !. .
  0 "|<"  CMN_tTokenMatch ? CMN_bOPCODE_SLX 0 CMN_cEmitInstr !. .
  0 "|>"  CMN_tTokenMatch ? CMN_bOPCODE_SRX 0 CMN_cEmitInstr !. .
  0 "<-"  CMN_tTokenMatch ? CMN_bOPCODE_INP 0 CMN_cEmitInstr !. .
  0 "->"  CMN_tTokenMatch ? CMN_bOPCODE_OUT 0 CMN_cEmitInstr !. .
  0 "<?"  CMN_tTokenMatch ? CMN_bOPCODE_INU 0 CMN_cEmitInstr !. .
  0 ">0"  CMN_tTokenMatch ? CMN_bOPCODE_TRA 0 CMN_cEmitInstr !. .
  0 ">8"  CMN_tTokenMatch ? CMN_bOPCODE_TRA 1 CMN_cEmitInstr !. .
  0 ">16" CMN_tTokenMatch ? CMN_bOPCODE_TRA 2 CMN_cEmitInstr !. .
  0 ">32" CMN_tTokenMatch ? CMN_bOPCODE_TRA 3 CMN_cEmitInstr !. .

  0 "-->" CMN_tTokenMatch ?
    CMN_bOPCODE_DES CMN_bDES_LOOP CMN_cEmitInstr
    1 _CMN_cNoPopSet CMN_bOPCODE_JNA 0 CMN_cEmitInstr 0 _CMN_cNoPopSet
    ~16 $CMN_bCurrentInstr>_CMN_g2P2 ~0
    ~32 $CMN_bCurrentInstrAddr ~0
    _CMN_cPrepareUnknownAddr
    CMN_bOPCODE_OUT 0 CMN_cEmitInstr
    CMN_bOPCODE_DES CMN_bDES_ENDLOOP CMN_cEmitInstr
    CMN_bOPCODE_JMA 0 CMN_cEmitInstr
    CMN_cFillConst
    ~16 $_CMN_g2P2>_CMN_g2P1 ~0
    ~32 $CMN_bCurrentInstrAddr ++ ~0
    _CMN_cFillBackAddr
    CMN_bOPCODE_POP 0 CMN_cEmitInstr
    !.
  .

  ~16 ^ 0 ~0 # nothing matched
.

# arg: 0 sn ... s1 s0
# ret: 3n
# Converts ptr name to BC ptr index, for user ptrs (those not named "0", "1"
# etc.) currently set TE plays a role. -1 is returned on error.
_CMN_ptrNameToIndex:
  $1 0 = $1 "0" >= $1 & "9" <= & ?
    ~32 0 ~0 "0" - >32 ^
  ;
    CMN_cStrPseudohash "0" _CMN_cCurrentTypeEnvGet +
    CMN_cSymbolEncode ~32 $2 $2 $2 ~0 _CMN_cSymbolTableFind ?
      ~32 ^ _CMN_cSymbolGetIndex 16 + ~0
    ;
      ~32 ^ ^ ^ ^ -1 ~0
    .
  .
.

_CMN_cHandlePtrCommand:
  0 "$"  CMN_tTokenMatch ? CMN_bOPCODE_PUX 0 CMN_cEmitInstr !. .
  0 "$$" CMN_tTokenMatch ? CMN_bOPCODE_ADR 0 CMN_cEmitInstr !. .

  _CMN_tTokenToStr ^ # convert to str and drop "$"

  $0 ":" = $1 "+" = | ?
    ":" = CMN_bOPCODE_MEX CMN_bOPCODE_PAX ??  0 CMN_cEmitInstr

    _CMN_ptrNameToIndex

    ~32
      $0 -1 != ?
        CMN_cFillConst
      ;
        ^
        ~0 CMN_cSTATE_ERROR_NAME_UNK $:CMN_cState ~32
      .
    ~0

    !.
  .

  $0 ">" = $1 "<" = | ?
    ~32 0 ~0 ">" = 1 15 ?? >32 # const for PAC, 15 is -1 in 4b two's compl.

    CMN_bOPCODE_PAC 0 CMN_cEmitInstr
    _CMN_ptrNameToIndex

    ~32
      $0 -1 != ?
        >< CMN_cFillConsts
      ;
        ^ ^
        ~0 CMN_cSTATE_ERROR_NAME_UNK $:CMN_cState ~32
      .
    ~0

    !.
  .

  $0>_CMN_gP1

  @@
    $_CMN_gP1 0 = ?   0 !@ .
    $_CMN_gP1 ">" = ? 0 $:_CMN_gP1 1 !@ . # we insert 0 to separate the strs
    $_CMN_gP1 "=" = ? 0 $:_CMN_gP1 2 !@ .
    $<_CMN_gP1
  .

  ?'
    # $ptr>ptr2 or $ptr=ptr2
    1 = CMN_bOPCODE_PCO CMN_bOPCODE_PCM ??  0 CMN_cEmitInstr

    _CMN_ptrNameToIndex _CMN_ptrNameToIndex

    ~32
      ><
      $1 -1 != $1 -1 != & ?
        CMN_cFillConsts
      ;
        ^ ^ ~0 CMN_cSTATE_ERROR_NAME_UNK $:CMN_cState ~32
      .
    ~0
  ;
    ^
    # $ptr
    CMN_bOPCODE_MGE 0 CMN_cEmitInstr
    _CMN_ptrNameToIndex

    ~32
      $0 -1 != ?
        CMN_cFillConst
      ;
        ^ ~0 CMN_cSTATE_ERROR_NAME_UNK $:CMN_cState ~32
      .  
    ~0
  .
.

# arg: 0 sn ... s1 s0
# ret: c14 c13 ... c0
# Converts a zero term. ASCII str. of any length into "pseudohash" usable in
# symbol table. The pseudohash consists of exactly 15 ASCII characters (16th not
# used to leave space for one type char.) and will be valid identifier (that can
# be used e.g. by a transpiler) that will try to resemble the original str. (for
# human readability). The result will NOT be zero term. (do not print it!).
CMN_cStrPseudohash:
  $0>_CMN_gP1

  0 # sum of all chars, used in hash
  0 # incrementing product, used in hash, depends on char order
  0 # length of string, used in hash
 
  @@ # go down and compute all the vals
    $_CMN_gP1

    $0 0 = ?
      ^
      !@
    .

    $0 $4 + $:4
    $2 ++ * $:2
    ++

    $<_CMN_gP1
  .

  $0 15 <= ?
    # extend from left
    >< ^ >< ^
    15 >< -

    @'
      $0>_CMN_gP1
      $0

      1 @
        $<_CMN_gP1 $_CMN_gP1 $>_CMN_gP1 $:_CMN_gP1 $<_CMN_gP1
        $_CMN_gP1
      .

      "_" $>_CMN_gP1 $:_CMN_gP1

      --
    .

    ^
  ;
    # shorten and insert three char hash
    $0>_CMN_gP1 $0 $:_CMN_gV1

    CMN_cNumToChar
    >< CMN_cNumToChar
    $2 CMN_cNumToChar $:3

    $_CMN_gV1 7 - -1 * $+0
    $_CMN_gP1 $<_CMN_gP1
    $_CMN_gP1 $<_CMN_gP1
    $_CMN_gP1 $<_CMN_gP1
    $_CMN_gP1
  .

  # shift everything one down (overwrite the bottom 0)

  $0>_CMN_gP1

  1 @
    $<_CMN_gP1
    $_CMN_gP1
  .

  1 @
    $>_CMN_gP1 $_CMN_gP1 $<_CMN_gP1 $:_CMN_gP1
    $>_CMN_gP1
    $0=_CMN_gP1
  .

  ^
.

# arg: c14 c13 ... c0 c
# ret: 3v1 3v2 3v3
# Encodes a symbol made of string chars (pseudohash) and one type char (c) to 3
# 32bit vals that may be directly stored in symbol table.
CMN_cSymbolEncode:
  ~32
    0 ~0 CMN_cCharToNum >32 ~32 $:_CMN_g3V1

    3 @'
      0

      5 @'
        ><
        6 |< 0 ~0 CMN_cCharToNum >32 ~32 |
        >< --
      .
      ^

      2 |< $_CMN_g3V1 +b11 & |
      $_CMN_g3V1 2 |> $:_CMN_g3V1

      >< --
    .
    ^
  ~0
.

# arg: 3v1 3v2 3v3
# ret: c14 c13 ... c0 c
# Performs the opposite of CMN_cSymbolEncode.
CMN_cSymbolDecode:
  ~32
    0 $:_CMN_g3V1

    3 @'
      ><

      $0 +b11 &
      $_CMN_g3V1 2 |< |
      $:_CMN_g3V1

      2 |>

      5 @'
        ><
        ~0 0 ~32 $0 >0 ~0 +b111111 & CMN_cNumToChar ~32
        6 |>
        >< --
      .
      ^
 
      ^
      --
    .
    ^

    ~0 0 ~32
    $_CMN_g3V1 >0 CMN_cNumToChar
  ~0
.

# arg: addrSize
# Initializes com, addrSize says the default addr size, currently either 0
# (16bit) or 1 (32bit), higher val will generate bigger BC (more space will be
# allocated for unknown addres) but also allow compilation of larger programs
# (note that bigger generated BC can still be later optimized to smaller size,
# but initially needs more RAM).
CMN_cInit:
  ~32 0 ~0 >32

  CMN_cSTATE_OK $:CMN_cState

  ~32
    CMN_bBYTECODE_SIZE
    0 ~0 CMN_bHEADER_SIZE 1 |> >32 ~32
    < ?
      ~0 CMN_cSTATE_ERROR_BC_TOO_BIG $:CMN_cState ~32
      !.
    .

    1 & 28 |< $:_CMN_cInterState

    $CMN_cMem>_CMN_cMemEnd
    CMN_cMEM_SIZE $+_CMN_cMemEnd

    ~16 $CMN_bBytecode>CMN_bCurrentInstr ~32

    CMN_bBYTECODE_SIZE @' # zero whole BC
      ~16 0 $:CMN_bCurrentInstr $>CMN_bCurrentInstr ~32
      --
    .
    ^

    0 CMN_bCurrentInstrSetAddr

    ~0
      CMN_bOPCODE_NOP 0 0 0 0 CMN_bMakeInstr ~16 0 ~0 >16
      ~16 $:CMN_bCurrentInstr ~0
    ~32
  ~0

  # set the BC end ptr:
  ~16 $CMN_bBytecode>_CMN_cBytecodeEnd ~0
  ~32
    CMN_bBYTECODE_SIZE # shift with loop, in case BC is bigger than 2^16
    @' ~16 $>_CMN_cBytecodeEnd ~32 -- .
    ^
  ~0

  CMN_tInit

  ~16 0 ~0 CMN_bOPCODE_INI 0 0 0 0 CMN_bMakeInstr >16 ~16 $:CMN_bCurrentInstr ~0
  _CMN_cPrepareUnknownAddr # here maybe we'll later insert a jump to init func
.

# ret: 3size
# Gets current size of symbol table (in items).
_CMN_cSymbolTableSize: ~32 $_CMN_cInterState +xffff & ~0 .

# arg: 3size
_CMN_cSymbolTableSizeSet:
  ~32 $_CMN_cInterState +xffff0000 & | $:_CMN_cInterState ~0
.

# arg: 3v1 3v2 3v3
# ret: succ 3val
# Finds symbol in symbol table, returns 1 (in TE 0) and the symbol val (in TE
# 32) on success, otherwise 0 (in both TE 0 and 32).
_CMN_cSymbolTableFind:
  ~32
    $CMN_cMem>_CMN_g3P1

    _CMN_cSymbolTableSize @'
      $_CMN_g3P1 $2 = $>_CMN_g3P1
      $_CMN_g3P1 $4 = $>_CMN_g3P1
      $_CMN_g3P1 $6 = $>_CMN_g3P1

      & & ?
        ^ ^ ^ ^ $_CMN_g3P1 ~0 1 ~32 # found
        !.
      .

      $>_CMN_g3P1 # skip the symbol value
      --
    .
    ^
  ~0

  ~32 ^ ^ ^ 0 ~0 0 # not found
.

# arg: 3val 3v1 3v2 3v3
# Appends an encoded symbol with given val to the symbol table, handles errors.
_CMN_cSymbolTableAdd:
  ~32
    $CMN_cMem>_CMN_g3P1
    _CMN_cSymbolTableSize $0 2 |< 

    3 + # this is here for the if check below
    $+_CMN_g3P1

    $_CMN_g3P1=_CMN_cMemEnd 2 = ?
      -3 $+_CMN_g3P1 # check done, shift back

      ++ _CMN_cSymbolTableSizeSet

      $:_CMN_g3P1 $>_CMN_g3P1
      $:_CMN_g3P1 $>_CMN_g3P1
      $:_CMN_g3P1 $>_CMN_g3P1
      $:_CMN_g3P1
    ;
      ^ ~0 CMN_cSTATE_ERROR_SYMBOL_TAB $:CMN_cState ~32
    .
  ~0
.

# arg: n
# ret: c15 c14 ... c0 c 3val
# Gets and decodes Nth symbol and its val, N must be smaller than the num of
# items in the table (this isn't checked).
CMN_cSymbolGet:
  ~32 0 ~0 >32

  ~32
    $CMN_cMem>_CMN_g3P1
    ++ 2 |<
    $+_CMN_g3P1

    $<_CMN_g3P1 $_CMN_g3P1
    $<_CMN_g3P1 $_CMN_g3P1
    $<_CMN_g3P1 $_CMN_g3P1
    $<_CMN_g3P1 $_CMN_g3P1
    
    CMN_cSymbolDecode
  ~0
.

# arg: 3v1 3v2 3v3
# ret: 3index
# Returns index of given symbol among the symbols of the same type (starting
# with the same char). Doesn't check if the symbol exists.
_CMN_cSymbolGetIndex:
  ~32 $2 $2 $2 ~0 CMN_cSymbolDecode
  $0 $:_CMN_gV1
  _CMN_cPseudohashPop
  $_CMN_gV1 # type char

  ~32
    0 # result
    $CMN_cMem>_CMN_g3P1
    $>_CMN_g3P1
    $>_CMN_g3P1

    _CMN_cSymbolTableSize @'
      $_CMN_g3P1 $<_CMN_g3P1
      $_CMN_g3P1 $<_CMN_g3P1
      $_CMN_g3P1
      6 $+_CMN_g3P1

      # stack 32 now: v1 v2 v3 r i a b c

      $7 $3 = $7 $3 = $7 $3 = & & ?
        ^ ^ ^
        !@
      .

      ~0
        CMN_cSymbolDecode
        $0 $:_CMN_gV1
        _CMN_cPseudohashPop

        $0 $_CMN_gV1 = ?
          ~32 $1 ++ $:2 ~0
        .
      ~32

      --
    .
    ^

    >< ^ >< ^ >< ^
  ~0

  ^ # type char
.

# arg: c14 c13 ... c0 c
# ret:
_CMN_cPseudohashPop: ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . # can be just one instr

# Helper func for the end iteration on symbol table. Expects _CMN_g3P2 to
# point to a symbol. If the symbol's val is not 0, pushes 1 to TE 0 (else 0) and
# decodes the symbol (else not). Shifts the pointer to following symbol.
_CMN_cEndSymbolIterate:
  ~32
    3 $+_CMN_g3P2 $_CMN_g3P2 ?
      $<_CMN_g3P2 $_CMN_g3P2
      $<_CMN_g3P2 $_CMN_g3P2
      $<_CMN_g3P2 $_CMN_g3P2
      CMN_cSymbolDecode
      4 $+_CMN_g3P2
      ~0 1 ~32
    ;
      # symbol value = 0, not interested (such ptr doesn't need addr)
      ~0 0 ~32 
      $>_CMN_g3P2
    .
  ~0
.

# arg: 3v c
# ret: s14 ... s1 s0
# Helper for end pass, find symbol of given type (c) with given value, returns
# its pseudohash, relies on the symbol existing.
_CMN_cFindSymWithVal:
  ~32 0 ~0 >32 ~32 >< ~0 # let's store the char in TE 32

  ~32
    $CMN_cMem>_CMN_g3P2
    3 $+_CMN_g3P2

    _CMN_cSymbolTableSize @' # search the symbol table
      $1 $_CMN_g3P2 = ?      # val of symbol = instr const?
        $<_CMN_g3P2 $_CMN_g3P2
        $<_CMN_g3P2 $_CMN_g3P2
        $<_CMN_g3P2 $_CMN_g3P2
        3 $+_CMN_g3P2

        CMN_cSymbolDecode

        ~0
          0 ~32 $2 >0 ~0 = ?
            !@
          ;
            "a" # add back one popped char
            _CMN_cPseudohashPop
          .
        ~32

      . # symbol const comparison

      4 $+_CMN_g3P2
      --
    . # for each symbol
    ^

    ^ ^ # val, c
  ~0
.

# Helper, performs the final BC pass after compiler received last input char.
_CMN_cEndPass:
  ~32 $CMN_bCurrentInstrAddr ++ ~0 # BC end addr (for exits)

  # resolve unknown jumps AND check for unhandled DES instrs (HACK):
  ~16
    $CMN_bCurrentInstr @' # for each instr (relying on header ending in 0 0)
      ~0 0 ~16 >0
      ~0
        $0 CMN_bInstrOpcode CMN_bOPCODE_DES = ?
          $0 CMN_bInstrWontPop ?
            # DES with no-pop bit set (see HACK)
            $0 8 |> +x0f & CMN_bDES_EXIT = ?
              ~16
                $CMN_bCurrentInstr +xdfff & $:CMN_bCurrentInstr # unmark
                $>CMN_bCurrentInstr
              ~0

              ~32 $0 CMN_cFillConst ~0 # fill the BC end addr
            ; # other than exit means unhandled control struct (error)
              ^
              CMN_cSTATE_ERROR_UNEXPECTED $:CMN_cState
              !@
            .
          ;
          $0 8 |> +b1111 & CMN_bDES_GOTO = ?
            ~16 $>CMN_bCurrentInstr ~0
            CMN_bCurrentInstrGetConst32 "g" _CMN_cFindSymWithVal
            "l" CMN_cSymbolEncode _CMN_cSymbolTableFind ?
              ~16
                $CMN_bCurrentInstr>_CMN_g2P2
                _CMN_cPrepareUnknownAddr # delete the current const
                $_CMN_g2P2>CMN_bCurrentInstr
                CMN_cFillConst
              ~0
            ;
              ~32 ^ ~0
              CMN_cSTATE_ERROR_NAME_UNK $:CMN_cState # goto to unknown label
            .

            ~16 0 ~0
            CMN_bOPCODE_DES 0 0 CMN_bDES_GOTO 0 CMN_bMakeInstr >16

            ~16
              1 @
                $<CMN_bCurrentInstr
                $0 $CMN_bCurrentInstr !=
              .
              
              $<CMN_bCurrentInstr
              ^
            ~0
          . .
        .

        CMN_bInstrOpcode CMN_bOPCODE_CAE = ? # unknown call?
          # find matching "f" symbol for the "c" symbol
          CMN_bCurrentInstrGetConst32 "c" _CMN_cFindSymWithVal

          "f" CMN_cSymbolEncode _CMN_cSymbolTableFind ?
            ~16 0 ~0 CMN_bOPCODE_CAL 0 0 0 0 CMN_bMakeInstr >16
            ~16
              $:CMN_bCurrentInstr
              $CMN_bCurrentInstr>_CMN_g2P2
              _CMN_cPrepareUnknownAddr
              $_CMN_g2P2>CMN_bCurrentInstr
              CMN_cFillConst
            ~0
          ;
            ~32 ^ ~0
          .
        . # instr opcode = CAE?
      ~16

      $<CMN_bCurrentInstr
      $CMN_bCurrentInstr
    . # for all instrs
    ^
  ~0

  ~32 -- ~0 CMN_bCurrentInstrSetAddr # we use the above pushed addr to get back

  # now potentially make an init func and set ptr addresses
  0 # make separate init func?

  ~32
    $CMN_cMem>_CMN_g3P2

    _CMN_cSymbolTableSize  # counter
    @' # iterate on symbol table
      ~0 _CMN_cEndSymbolIterate ?
        $0 $:_CMN_gV1 _CMN_cPseudohashPop

        $_CMN_gV1 $0 "0" >= >< "3" <= & ?
          ~32 -4 $+_CMN_g3P2 ~0 # we'll want this symbol again
          ^ 1
          !@
        .
      .
      ~32

      --
    .
    # not popping the counter yet
  ~0

  ? # Make init func? We still have symbol ptr in _CMN_g3P2 and count. in TE 32.
    ~32 0 >< 0 >< 0 >< 0 >< ~0 # a3, a2, a1, a0: last addr in each TE

    0 _CMN_cCurrentTypeEnvSet
    CMN_bOPCODE_DES CMN_bDES_FUNDEF CMN_cEmitInstr
    CMN_bOPCODE_JMA 0 CMN_cEmitInstr _CMN_cPrepareUnknownAddr

    ~16 0 ~0 CMN_bOPCODE_CAL 0 0 0 0 CMN_bMakeInstr >16
    ~16 0 ~0 CMN_bHEADER_SIZE 1 |> >16
    ~16 $CMN_bBytecode>_CMN_g2P1 $+_CMN_g2P1 $:_CMN_g2P1 ~0
    ~32 $CMN_bCurrentInstrAddr ++ ~0 _CMN_cFillBackAddr

    ~32
    @' # continue iterating on symbols, we stopped at first we shall handle
      ~0 _CMN_cEndSymbolIterate ?
        $0 $0 "0" >= >< "3" <= & ?
          $0 "0" - _CMN_cCurrentTypeEnvSet
          ~32
            $<_CMN_g3P2 $_CMN_g3P2
            $<_CMN_g3P2 $_CMN_g3P2
            $<_CMN_g3P2 $_CMN_g3P2
            $<_CMN_g3P2 $_CMN_g3P2
            4 $+_CMN_g3P2

            _CMN_cSymbolGetIndex

            # now we have on stack 32: 
            # a3 a2 a1 a0 i symbolVal ptrID

            16 + # user pointers start at ID 16
            ><

            $0>_CMN_g3P1
            0 ~0 $0 >32 ~32 "0" - -1 * 3 -
            $+_CMN_g3P1 # set the ptr to aN

            $_CMN_g3P1 +
            $_CMN_g3P1
            >< $:_CMN_g3P1
          ~0

          ~32 ?' ~0 # only set ptr whose value is not 0 (0 is default)
            CMN_bOPCODE_PSC 0 CMN_cEmitInstr
            CMN_cFillConsts
          ;
            ~32 ^ ^ ~0
          .
        .

        _CMN_cPseudohashPop
      .
      ~32

      --
    . # symbol iterate loop

    ^ # counter

    4 @' # pop a0, a1, a2, a3, potentially setting addr of ptr 0
      ><

      ?'
        ~0 0 ~32 4 $2 - >0 _CMN_cCurrentTypeEnvSet
        ~0 CMN_bOPCODE_PSC 0 CMN_cEmitInstr ~32
        0 >< CMN_cFillConsts
      ;
        ^
      .

      --
    .
    ^
    ~0

    0 _CMN_cCurrentTypeEnvSet

    CMN_bOPCODE_INI 0 CMN_cEmitInstr
    CMN_bOPCODE_RET 0 CMN_cEmitInstr

    # now fill back the JMA jump addr
    ~16 0 ~0 CMN_bOPCODE_JMA 0 0 0 0 CMN_bMakeInstr >16

    ~16
      $CMN_bCurrentInstr>_CMN_g2P1

      1 @    
        $<_CMN_g2P1
        $0 $_CMN_g2P1 !=
      .

      ^ # comparison instr
    ~0

    ~32 $CMN_bCurrentInstrAddr ++ ~0 _CMN_cFillBackAddr
  ;
    ~32 ^ ~0 # counter
  . # init func?

  ~16
    # fill the header:
    CMN_bChecksum

    $CMN_bBytecode>_CMN_g2P1
    +x4243 $:_CMN_g2P1 # magic number "CB"

    $>_CMN_g2P1 $>_CMN_g2P1 $:_CMN_g2P1
  ~0

  $CMN_cState CMN_cSTATE_OK = ?
    CMN_cSTATE_DONE $:CMN_cState
  .
.

# arg: s14 ... s1 s0 c
# ret:
# Helper for handling encounters of unknown jumps (calls, gotos, ...), checks
# prepares unknown addr. and fills in a temporary ID that given pseudohash has
# in sym. table (will be added if it doesn't exist).
_CMN_cHandleUnknownJump:
  _CMN_cPrepareUnknownAddr
  _CMN_cUnknownJumpCount # unknown jump ID
  CMN_cSymbolEncode      # 32 stack now: callID v3 v2 v1

  ~32 $2 $2 $2 ~0 _CMN_cSymbolTableFind ?
    ~32 $:4 ^ ^ ^ ~0 # rewrite the ID by the one we got, pop the rest
  ;
    ~32 ^ ~0 # pop the zero returned by _CMN_cSymbolTableFind
    _CMN_cSymbolTableAdd
    _CMN_cUnknownJumpCount # we consumed this, so push again
    _CMN_cUnknownJumpCountInc
  .

  ~16 
    0 ~0 _CMN_cDefaultAddrSize >16 ~16 -1 *
    $CMN_bCurrentInstr>_CMN_g2P1
    $+_CMN_g2P1
    $>_CMN_g2P1
  ~0

  _CMN_cFillBackAddr # temporarily fill the ID to CAE
.

# arg: char
# Feeds the next character to com, which may or may not be popped by this func,
# which must be left so -- the behavior is similar to CMN_tFeedChar. Nothing is
# returned, state will be set in CMN_cState. Compiler will be using parser and
# generating instrs into CMN_bBytecode using CMN_bCurrentInstr which therefore
# mustn't be modified while com is working. When com finishes (either with succ
# or error), it will clean all trash on main stack (e.g. partially parsed tok.).
CMN_cFeedChar:
  # WARNING: do NOT exit (!.) inside this function, it would skip the end code

  $CMN_cState CMN_cSTATE_OK = ?
    ~16 0 ~0 $0 0 = >16 # end? we temporarily store this in TE 16 (can't use
                        # TE 0 because we have token there
    CMN_tFeedChar

    $0 CMN_tTOKEN_NONE = ?
      ^ # token type
    ;
      $1 "'" = ? # no pop?
        >< ^     # remove the last "'"
        1
      ;
        0
      .

      _CMN_cNoPopSet

      $0 CMN_tTOKEN_COMMAND = ?
        ^ # token type

        $_CMN_tSTBackup>_CMN_gP1
        $>_CMN_gP1

        $_CMN_gP1 "~" = ?
          $>_CMN_gP1

          $_CMN_gP1 "0" = ?  0 _CMN_cCurrentTypeEnvSet  ;
          $_CMN_gP1 "8" = ?  1 _CMN_cCurrentTypeEnvSet  ;
          $_CMN_gP1 "1" = ?  2 _CMN_cCurrentTypeEnvSet  ;
          $_CMN_gP1 "3" = ?  3 _CMN_cCurrentTypeEnvSet  ;
          $_CMN_gP1 34 = ?
            # ~"...", file include

            ^ # remove "
            _CMN_tTokenToStr ^ ^ # remove ~"

            ~32 0 ~0 # symbol value for later

            CMN_cStrPseudohash "i" CMN_cSymbolEncode

            ~32 $2 $2 $2 _CMN_cSymbolTableFind ^ ~0 ?
              ~32 ^ ^ ^ ^ ~0 # pop encoded and symbol val
              # repeated include, ignore
            ;
              _CMN_cSymbolTableAdd
              _CMN_tTokenToStr ^ ^ # remove ~"
              CMN_cInclude
            .
          ; 
          $_CMN_gP1 ":" = ? # label?
            _CMN_tTokenToStr ^ ^ # remove ~:
            CMN_cStrPseudohash "l"
            ~32 $CMN_bCurrentInstrAddr ++ ~0
            CMN_cSymbolEncode

            ~32 $2 $2 $2 _CMN_cSymbolTableFind ^ ~0 ?
              ~32 ^ ^ ^ ^ ~0
              CMN_cSTATE_ERROR_NAME_EXISTS $:CMN_cState
            ;
              _CMN_cSymbolTableAdd
            .
          ; # ptr decl.
            $0>_CMN_gP1
            0 # does token contain ':'?

            1 @
              $_CMN_gP1 ":" = ?
                ^ 1 !@
              .

              $<_CMN_gP1
              $_CMN_gP1=_CMN_tSTBackup
            .

            ? # size present
              CMN_tLiteralVal 1 = ?
                1 @ ":" != . # delete the part after ':'
              ;
                ~32 ^ ~0
                CMN_cSTATE_ERROR_TOKEN $:CMN_cState
              .
            ;
              ~32 1 ~0 # no size specified implies 1
            .

            _CMN_tTokenToStr ^ # pop removes '~'
            CMN_cStrPseudohash _CMN_cCurrentTypeEnvGet "0" +
            CMN_cSymbolEncode

            ~32 $2 $2 $2 _CMN_cSymbolTableFind ^ ~0 ?
              ~32 ^ ^ ^ ~0
              CMN_cSTATE_ERROR_NAME_EXISTS $:CMN_cState
            ;
              _CMN_cSymbolTableAdd
            .
          . . . . . .
        ;
        $_CMN_gP1 "$" = ?
          _CMN_cHandlePtrCommand
        ;
          _CMN_cHandleSimpleCommand

          ~16 !! ? ~0 # no command matched?
            # now we suppose it's goto (only thing left)
            CMN_bOPCODE_DES CMN_bDES_GOTO CMN_cEmitInstr
            CMN_bOPCODE_JMA 0 CMN_cEmitInstr
            _CMN_tTokenToStr ^ CMN_cStrPseudohash "g"
            _CMN_cHandleUnknownJump
          .
        . .
      ;
      $0 CMN_tTOKEN_NAME = ?
        ^ # token type

        _CMN_tTokenToStr CMN_cStrPseudohash "f" CMN_cSymbolEncode

        ~32 $2 $2 $2 ~0 # keep these for the later else branch

        _CMN_cSymbolTableFind ?
          CMN_bOPCODE_CAL 0 CMN_cEmitInstr
          CMN_cFillConst # fill the found addr
          ~32 ^ ^ ^ ~0
        ;
          # symbol not found, this is NOT an error, func may be defined later
          ~32 ^ ~0 # pop the zero returned by _CMN_cSymbolTableFind

          CMN_bOPCODE_CAE 0 CMN_cEmitInstr # for now emit CAE
          CMN_cSymbolDecode ^ "c" # change type from "f" to "c"
          _CMN_cHandleUnknownJump
        .
      ;
      $0 CMN_tTOKEN_FUNC_DEF = ?
        ^ # token type

        CMN_bOPCODE_DES CMN_bDES_FUNDEF CMN_cEmitInstr
        ~16 $CMN_bCurrentInstr +x2000 | $:CMN_bCurrentInstr ~0 # mark, HACK
        CMN_bOPCODE_JMA 0 CMN_cEmitInstr
        _CMN_cPrepareUnknownAddr

        # add to symbol table:
        ^ # remove the ":"
        _CMN_tTokenToStr CMN_cStrPseudohash "f"

        ~32 $CMN_bCurrentInstrAddr ++ ~0 
        CMN_cSymbolEncode 

        ~32 $2 $2 $2 _CMN_cSymbolTableFind ^ ~0 # already exists?
        ?
          ~32 ^ ^ ^ ^ ~0
          CMN_cSTATE_ERROR_NAME_EXISTS $:CMN_cState
        ;
          _CMN_cSymbolTableAdd
        .
      ;
      $0 CMN_tTOKEN_NUMBER = ?
        ^ # token type

        CMN_tLiteralVal

        $0 !! ?
          CMN_cSTATE_ERROR_TOKEN $:CMN_cState
          ~32 ^ ~0
        ;
          # mask out only needed bits, to not have unnecessary COCs:
          _CMN_cCurrentTypeEnvGet 1 = ?  ~32 +xff & ~0   ;
          _CMN_cCurrentTypeEnvGet 2 = ?  ~32 +xffff & ~0
          . .

          1 _CMN_cNoPopSet
          CMN_bOPCODE_CON 0 CMN_cEmitInstr

          $0 2 = _CMN_cCurrentTypeEnvGet 0 = & ? # TE 0 and negative lit.?
            ~32 0 ~0 CMN_cFillConst
            CMN_bOPCODE_SUC 0 CMN_cEmitInstr
            ~32 ! ++ ~0  CMN_cFillConst
          ;
            CMN_cFillConst
          .
        .

        ^
      ;
      $0 CMN_tTOKEN_STRING = ?
        ^ # token type

        $0>_CMN_gP1

        @@
          $<_CMN_gP1

          $_CMN_gP1 34 = ? # double quotes?
            !@
          .

          1 _CMN_cNoPopSet
          CMN_bOPCODE_CON 0 CMN_cEmitInstr

          ~32 0 ~0 $_CMN_gP1 >32 CMN_cFillConst
        .
      ;
        # CMN_tTOKEN_ERROR
        ^ # token type
      . . . . .

      CMN_tPopToken
    . # token detected?

    ~16 ? ~0 # end? this was pushed at very top
      _CMN_cEndPass
    .
  ;
    ^ # input char
  . # state OK?
.
